package com.lagou.muticodec;

import com.lagou.util.HexUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.TooLongFrameException;
import static io.netty.util.internal.ObjectUtil.checkPositive;

import java.util.Arrays;
import java.util.List;

public class JsonAndDelimiterFrameDecoder extends ByteToMessageDecoder {

    private static final int ST_CORRUPTED = -1;
    private static final int ST_INIT = 0;
    private static final int ST_DECODING_NORMAL = 1;
    private static final int ST_DECODING_ARRAY_STREAM = 2;

    private int openBraces;
    private int idx;

    private int lastReaderIndex;

    private int state;
    private boolean insideString;
    private int frameIndex=0;
    private boolean delimiterFrame=false;

    private ByteBuf buf = Unpooled.buffer(0);
    private byte fisrtByte='{';

    private final int maxObjectLength;
    private byte[] delimiters;

    private final boolean streamArrayElements;

    public JsonAndDelimiterFrameDecoder() {
        // 1 MB
        this(1024 * 1024);
    }

    public JsonAndDelimiterFrameDecoder(int maxObjectLength) {
        this(maxObjectLength, false);
    }

    public JsonAndDelimiterFrameDecoder(boolean streamArrayElements) {
        this(1024 * 1024, streamArrayElements);
    }

    /**
     * @param maxObjectLength   maximum number of bytes a JSON object/array may use (including braces and all).
     *                             Objects exceeding this length are dropped and an {@link TooLongFrameException}
     *                             is thrown.
     * @param streamArrayElements   if set to true and the "top level" JSON object is an array, each of its entries
     *                                  is passed through the pipeline individually and immediately after it was fully
     *                                  received, allowing for arrays with "infinitely" many elements.
     *
     */
    public JsonAndDelimiterFrameDecoder(int maxObjectLength, boolean streamArrayElements) {
        this.maxObjectLength = checkPositive(maxObjectLength, "maxObjectLength");
        this.streamArrayElements = streamArrayElements;
    }

    public JsonAndDelimiterFrameDecoder(int maxObjectLength, byte[] delimiters) {
        this(maxObjectLength, false);
        this.delimiters = delimiters;
    }

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        if (state == ST_CORRUPTED) {
            in.skipBytes(in.readableBytes());
            return;
        }

        if (this.idx > in.readerIndex() && lastReaderIndex != in.readerIndex()) {
            this.idx = in.readerIndex() + (idx - lastReaderIndex);
        }

        // index of next byte to process.
        int idx = this.idx;
        System.out.println("idx:"+idx);
        int wrtIdx = in.writerIndex();
        System.out.println("wrtIdx:"+wrtIdx);
        if (wrtIdx > maxObjectLength) {
            // buffer size exceeded maxObjectLength; discarding the complete buffer.
            in.skipBytes(in.readableBytes());
            reset();
            throw new TooLongFrameException(
                    "object length exceeds " + maxObjectLength + ": " + wrtIdx + " bytes discarded");
        }
        System.out.println("000000 buf.array().length:"+buf.array().length);
        for (/* use current idx */; idx < wrtIdx; idx++) {
            byte c = in.getByte(idx);
            //System.out.println("in.readerIndex():" + in.readerIndex());
            if (c == '{' && frameIndex == 0) {
                fisrtByte = c;
            }else if (c != '{' && frameIndex == 0) {
                delimiterFrame=true;
            }
            frameIndex++;
            buf.writeByte(c);
            System.out.println("XXXXX buf.array().length:"+buf.array().length);
            System.out.println("c:"+c);
            if (c == '}') {
                System.out.println("1111 buf.array().length:"+buf.array().length);
                out.add(buf);
                //System.out.println("buf.toString(CharsetUtil.UTF_8):"+buf.toString(CharsetUtil.UTF_8));
                System.out.println( "HexUtil.bytesToHexString(buf.array())1:"+ HexUtil.bytesToHexString(buf.array()));
                buf=Unpooled.buffer(0);
                buf.clear();
                System.out.println("33333 buf.array().length:"+buf.array().length);
                frameIndex = 0;
                delimiterFrame=false;
            }

            if(delimiterFrame){
                byte[] bty=new byte[delimiters.length];
                for (int i = 0; i < delimiters.length; i ++) {
                    if(idx+i<=wrtIdx) {
                        bty[i] = in.getByte(idx + i);
                    }else{
                        return;
                    }
                }
                if(Arrays.equals(delimiters,bty)) {
                    //System.out.println( "HexUtil.bytesToHexString(buf.array())1:"+ HexUtil.bytesToHexString(buf.array()));
                    byte[] tmp=HexUtil.subByte(delimiters,1,delimiters.length-1);
                    idx=idx+tmp.length;
                    //System.out.println( "tmp:"+ HexUtil.bytesToHexString(tmp));
                    buf.writeBytes(tmp);

                    System.out.println( "HexUtil.bytesToHexString(buf.array())2:"+ HexUtil.bytesToHexString(buf.array()));
                    System.out.println("2222 buf.array().length:"+buf.array().length);
                    out.add(buf);
                    //System.out.println( "AlexUtil.bytesToHexString(buf.array()):"+ AlexUtil.bytesToHexString(buf.array()));
                    buf = Unpooled.buffer(0);
                    buf.clear();
                    System.out.println("444444 buf.array().length:"+buf.array().length);
                    frameIndex = 0;
                    delimiterFrame = false;
                }
            }
        }
        in.skipBytes(in.readableBytes());
    }

    /**
     * Override this method if you want to filter the json objects/arrays that get passed through the pipeline.
     */
    @SuppressWarnings("UnusedParameters")
    protected ByteBuf extractObject(ChannelHandlerContext ctx, ByteBuf buffer, int index, int length) {
        return buffer.retainedSlice(index, length);
    }

    private void reset() {
        insideString = false;
        state = ST_INIT;
        openBraces = 0;
    }
}